package me.yricky.alia.appAbilities

import kotlinx.coroutines.*
import me.yricky.alia.App
import me.yricky.alia.utils.command.CommandInst
import me.yricky.alia.utils.command.Session
import java.io.*
import java.util.*

/**
 * @author Yricky
 * @date 2022/1/29
 */
class CmdManager {
    private val scope = CoroutineScope(Dispatchers.Default)

    private val cmdMap:MutableMap<String, CommandInst> = HashMap()
    private val loadedArcaneCmdMap:MutableMap<String, CommandInst> = HashMap()



    companion object{
        const val EXIT_CMD = "exit"
    }

    fun active() = defaultSession
    val defaultSession: Session = startCommandShellSession(System.`in`,System.out,System.err)

    fun startCommandShellSession(
        input: InputStream,
        output: OutputStream,
        error:OutputStream,
    ): Session {
        return Session(
            BufferedReader(input.reader()),
            PrintStream(output),
            PrintStream(error)
        ).apply {
            scope.launch(Dispatchers.IO) {
                o.println("Alia is a layout tool.")
                o.println("Alia is made by Yricky.")
                o.print(">")
                loadBasicCommands()
                var cmd :String?
                while (let{ cmd = i.readLine(); cmd ?: EXIT_CMD } != EXIT_CMD){
                    try {
                        val ret = handle("$cmd",this@apply)
                        if(ret != 0){
                            o.print("(!$ret)>")
                        }else{
                            o.print(">")
                        }
                    }catch (err:Throwable){
                        err.printStackTrace(e)
                        o.println()
                        o.print("(!err)>")
                    }
                }
            }
        }
    }



    private fun loadBasicCommands(){
        register("help"){ _,_,session ->
            session.apply {
                o.println("Commands:")
                o.println(cmdMap.map { it.key }.plus(EXIT_CMD).reduce{ cmd1, cmd2 -> "$cmd1,$cmd2"})

            }
            0
        }
        register("pwd"){ _,_,session ->
            session.o.println(File(".").canonicalPath)
            0
        }
        register("exec"){ _,args,session->
            if(args.size>1){
                Runtime.getRuntime().exec(args.subList(1,args.size).toTypedArray()).let{
                    scope.launch {
                        session.apply {
                            var l:String?
                            val inBuf = BufferedReader(InputStreamReader(it.inputStream))
                            while (true){
                                l = withContext(Dispatchers.IO){
                                    inBuf.readLine()
                                }
                                if(l == null){
                                    o.println()
                                    break
                                }else{
                                    o.println(l)
                                }
                            }
                        }
                    }
                }
                0
            }else{
                -1
            }

        }
        register("kts") { _, args, session ->
            if (args.size != 2) {
                session.o.println("Usage: kts <path to a file>")
                -2
            } else {
                when {
                    args[1].uppercase(Locale.getDefault()).endsWith(".KTS") -> {
                        try {
                            App.scriptEngine.eval(File(args[1]).reader())
                            0
                        }catch (e:Throwable){
                            e.printStackTrace(session.e)
                            -1
                        }.let {
                            session.o.println()
                            it
                        }
                    }
                    else -> {
                        session.o.println("Support format:\n*.kts")
                        -1
                    }
                }
            }
        }
    }

    private fun register(cmd:String,action: CommandInst){
        cmdMap[cmd] = action
    }

    fun handle(command: String,session: Session):Int{
        val args = command.split(" ")
        return if(args.isNotEmpty()){
            cmdMap[args[0]]?.invoke(args[0],args,session) ?:
            loadedArcaneCmdMap[args[0]]?.invoke(args[0],args,session) ?:
            -404
        }else{
            -20
        }
    }
}

